package org.hertsig.entities;

import static com.google.common.base.Preconditions.*;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.hertsig.mci.MagicCardsInfo;

import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

@Slf4j
@AllArgsConstructor(access=AccessLevel.PRIVATE) @Getter
public class Type {
	private static final Map<String, SuperType> SUPER_TYPES = Maps.uniqueIndex(Arrays.asList(SuperType.values()), Functions.toStringFunction());
	private static final Map<String, CardType> CARD_TYPES = Maps.uniqueIndex(Arrays.asList(CardType.values()), Functions.toStringFunction());
	private static final Joiner JOINER = Joiner.on(" ");
	
	public static Type.Builder builder() {
		return new Type.Builder();
	}
	
	public static Type parse(String typeAndPT) {
		String[] parts = typeAndPT.split("—");
		checkArgument(parts.length <= 2, "Got %s", typeAndPT);
		Type.Builder builder = new Type.Builder();
		
		for (String type : parts[0].split("\\s+")) {
			if (type.contains("/")) {
				// There's literally *one* creature without creature types, which doesn't have the mdash in the type line
				builder.setPowerAndToughness(type);
			}
			else {
				builder.addCardOrSuperType(type);
			}
		}
		if (parts.length == 2) {
			String subtypeLine = parts[1]
				.replace("(Loyalty: ", "$L")
				.replace("(Color Indicator:", "$C");
			
			boolean colorIndicator = false;
			for (String type : subtypeLine.split("\\s+")) {
				if (type.trim().isEmpty()) {
					continue;
				}
				
				if (type.contains("/")) {
					builder.setPowerAndToughness(type);
				}
				else if (type.trim().startsWith("$L")) {
					builder.setLoyalty(type.substring(2, type.length() - 1));
				}
				else if (type.equals("$C")) {
					colorIndicator = true;
				}
				else if (colorIndicator) {
					builder.addColorIndicator(type.endsWith(")") ? type.substring(0, type.length() - 1) : type);
				}
				else {
					builder.addSubtype(type);
				}
			}
		}
		
		return builder.build();
	}
	
	private final Set<SuperType> superTypes;
	private final Set<CardType> cardTypes;
	private final Set<String> creatureTypes;
	private final Set<String> spellTypes;
	private final Set<String> artifactTypes;
	private final Set<String> enchantmentTypes;
	private final Set<String> planeswalkerTypes;
	private final Set<String> landTypes;
	private final Set<String> planeTypes;
	private final PowerAndToughness powerAndToughness;
	private final Integer loyalty;
	
	public String getMainTypes() {
		StringBuilder sb = new StringBuilder();
		JOINER.appendTo(sb, superTypes);
		if (sb.length() > 0) {
			sb.append(" ");
		}
		JOINER.appendTo(sb, cardTypes);
		return sb.toString();
	}
	
	public String getSubTypes() {
		StringBuilder sb = new StringBuilder();
		boolean isTribal = superTypes.contains(SuperType.Tribal);
		if (isTribal) {
			JOINER.appendTo(sb, creatureTypes);
		}
		JOINER.appendTo(sb, artifactTypes);
		JOINER.appendTo(sb, enchantmentTypes);
		JOINER.appendTo(sb, landTypes);
		JOINER.appendTo(sb, planeswalkerTypes);
		if (!isTribal) {
			JOINER.appendTo(sb, creatureTypes);
		}
		JOINER.appendTo(sb, spellTypes);
		JOINER.appendTo(sb, planeTypes);
		return sb.toString();
	}
	
	@NoArgsConstructor(access=AccessLevel.PRIVATE)
	public static class Builder {
		private final Set<SuperType> superTypes = EnumSet.noneOf(SuperType.class);
		private final Set<CardType> cardTypes = EnumSet.noneOf(CardType.class);
		private final Set<String> creatureTypes = Sets.newLinkedHashSet();
		private final Set<String> spellTypes = Sets.newLinkedHashSet();
		private final Set<String> artifactTypes = Sets.newLinkedHashSet();
		private final Set<String> enchantmentTypes = Sets.newLinkedHashSet();
		private final Set<String> planeswalkerTypes = Sets.newLinkedHashSet();
		private final Set<String> landTypes = Sets.newLinkedHashSet();
		private final Set<String> planeTypes = Sets.newLinkedHashSet();
		private final Set<String> colorIndicators = Sets.newLinkedHashSet();
		private PowerAndToughness powerAndToughness;
		private Integer loyalty;
		
		public Builder addCardOrSuperType(String type) {
			if (SUPER_TYPES.containsKey(type)) {
				superTypes.add(SUPER_TYPES.get(type));
			}
			else if (CARD_TYPES.containsKey(type)) {
				cardTypes.add(CARD_TYPES.get(type));
			}
			else {
				throw new IllegalArgumentException(type);
			}
			return this;
		}
		
		public Builder addColorIndicator(String color) {
			colorIndicators.add(color);
			return this;
		}
		
		public Builder addSubtype(String type) {
			Types types = MagicCardsInfo.getTypes();
			if (types.getArtifactTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Artifact), type);
				artifactTypes.add(type);
			}
			else if (types.getCreatureTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Creature) || superTypes.contains(SuperType.Tribal), type);
				creatureTypes.add(type);
			}
			else if (types.getEnchantmentTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Enchantment), type);
				enchantmentTypes.add(type);
			}
			else if (types.getLandTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Land), type);
				landTypes.add(type);
			}
			else if (types.getPlaneswalkerTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Planeswalker), type);
				planeswalkerTypes.add(type);
			}
			else if (types.getSpellTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Instant) || cardTypes.contains(CardType.Sorcery), type);
				spellTypes.add(type);
			}
			else if (types.getPlaneTypes().contains(type)) {
				checkArgument(cardTypes.contains(CardType.Plane), type);
				planeTypes.add(type);
			}
			else {
				log.warn("Unlisted type {}, assuming creature type", type);
				creatureTypes.add(type);
			}
			return this;
		}
		
		public Builder setPowerAndToughness(String powerAndToughness) {
			checkState(this.powerAndToughness == null);
			checkArgument(cardTypes.contains(CardType.Creature));
			this.powerAndToughness = PowerAndToughness.parse(powerAndToughness);
			return this;
		}
		
		public Builder setLoyalty(String loyalty) {
			checkState(this.loyalty == null);
			checkArgument(cardTypes.contains(CardType.Planeswalker));
			this.loyalty = Integer.parseInt(loyalty);
			return this;
		}
		
		public Type build() {
			for (SuperType type : superTypes) {
				checkState(!Sets.intersection(type.getAllowed(), cardTypes).isEmpty());
			}
			return new Type(superTypes, cardTypes, creatureTypes, spellTypes, artifactTypes, enchantmentTypes, planeswalkerTypes, landTypes, planeTypes, powerAndToughness, loyalty);
		}
	}
}
